/* ***** BEGIN LICENSE BLOCK *****
 * Version: MPL 1.1/GPL 2.0/LGPL 2.1/commercial
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may use this file in compliance with the License.
 * Alternatively, if you can't copy with the License, namely if you do not
 * want to provide source code of your modifications to this file, you may
 * negotiate a commercial licence with the initial developer.
 * You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is COID.
 *
 * The Initial Developer of the Original Code is
 * PosAm.
 * Portions created by the Initial Developer are Copyright (C) 2003
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 * Brano Kemen
 *
 * Alternatively, the contents of this file may be used under the terms of
 * either the GNU General Public License Version 2 or later (the "GPL"), or
 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the MPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the MPL, the GPL or the LGPL.
 *
 * ***** END LICENSE BLOCK ***** */

#include <limits.h>
#include "server.h"
#include "plugin.h"
#include "coid/comm/binstream/filestream.h"

namespace coid {

////////////////////////////////////////////////////////////////////////////////
opcd PluginScannerCoid::load_cfg( bool firstrun )
{
    bifstream bif;

    charstr wd,wdx;
    directory::get_cwd(wd);

    wdx = wd;
    wd += "dev.conf";

    post_info(0)
        << (firstrun ? "loading" : "reloading")
        << "configuration file from '" << wd << "'" << EOM;


    bool isportset=0;

    charstr cfg;
    bif.open(wd);
    if( bif.is_open() )
    {
        txtstream txs(bif);
        opcd e;
        OPCD_CATCH_ERR( txs >> cfg, e );
        if(e)
            return e;
    }
    else
    {
        post_warning(0) << "no 'dev.conf' configuration file found in working directory '" << wd << "'" << EOM;

        if(firstrun) {
            charstr wdi = wd;
            wdi << ".install";

            bool has_install = directory::is_valid_file(wdi);

            post_warning(0) << "checking for 'dev.conf.install' ... "
                << (has_install ? "found, copying to dev.conf" : "missing")
                << EOM;

            if( 0 == directory::copy_file(wdi, wd) ) {
                return load_cfg(false);
            }
        }

        wdx += "device";
        if( directory::is_valid_directory( wdx ) )
        {
            _fload_all = true;
            wdx.append( directory::separator() );
            *_wds.add() = wdx;

            post_warning(0) << "'device' directory found in the working directory, loading all devices from '" << wdx << "'" << EOM;
            return 0;
        }

        return ersUNAVAILABLE "configuration file missing and no device dir found";
    }


    int fdef = 0;
    t_hash hash;

    token tcfg = cfg;
    token line;
    while( !tcfg.is_empty() )
    {
        line = tcfg.get_line();
        line.skip_ingroup( token::TK_whitespace() );

        if( line.is_empty()  || line.first_char() == '#' )
            continue;

        command_tokens cts(line);

        if( cts[0] == "port" )
        {
            if( isportset )
                return ersMISMATCHED "multiple 'port' keywords found";
            if( cts[1].is_empty() )
                return ersMISMATCHED "missing an argument for 'directory'";

            int port = cts[1].toint();

            if( port < 1  ||  port >= 0x10000 )
                return ersINVALID_PARAMS "invalid port argument";

            _port = port;
            isportset = true;
        }
        else
        if( cts[0] == "directory" )
        {
            //if( iswdset )
            //    return ersMISMATCHED "multiple 'directory' keywords found";
            if( cts[1].is_empty() )
                return ersMISMATCHED "missing an argument for 'directory'";

            charstr str = wdx;

            directory::append_path( str, cts[1] );
            directory::treat_trailing_separator( str, false );

            if( !directory::is_valid_directory(str) )
                return ersINVALID_PARAMS "invalid directory argument";

            str.append( directory::separator() );
            _wds.add()->takeover(str);

            //iswdset = true;
        }
        else
        if( cts[0] == "default" )
        {
            if( fdef != 0 )
                return ersMISMATCHED "multiple 'default' keywords found";
            if( cts[1].is_empty() )
                return ersMISMATCHED "missing an argument for 'default'";

            if( cts[1] == "none" )
                fdef = -1;
            else if( cts[1] == "all" )
                fdef = 1;
            else
                return ersMISMATCHED "unknown argument for 'default'";
        }
        else
        if( cts[0] == "service" )
        {
            if( cts[1].is_empty() )
                return ersMISMATCHED "missing service name in 'service'";

            if( hash.find_value( cts[1] ) )
                return ersMISMATCHED "service name already specified in 'service'";

            service_params sp;
            sp._name = cts[1];
            sp._port = 0;
            sp._load = true;

            uint n=2;
            while( !cts[n].is_empty() )
            {
                if( cts[n] == "port" )
                {
                    ++n;
                    token pn = cts[n];
                    if( pn.count_inrange( '0', '9' ) <  pn.len() )
                        return ersSYNTAX_ERROR "invalid port number";

                    uint pnn = pn.touint();
                    if( pnn == 0  ||  pnn >= 0x10000 )
                        return ersOUT_OF_RANGE "port number";
                    
                    sp._port = (ushort)pnn;
                    ++n;
                }
                else if( cts[n] == "dir"  ||  cts[n] == "directory" )
                {
                    ++n;
                    charstr pn = cts[n];
                    //if( !directory::is_valid(pn) )
                    //    return ersINVALID_PARAMS "unable to open the specified directory";

                    directory::get_cwd( sp._wd );
                    if( !directory::append_path( sp._wd, pn ) )
                        return ersINVALID_PARAMS "invalid path";

                    directory::treat_trailing_separator( sp._wd, true );

                    ++n;
                }
                else
                    return ersMISMATCHED "unrecognized option";
            }
            
            hash.insert_value(sp);
        }
        else
        if( cts[0] == "!service" )
        {
            if( cts[1].is_empty() )
                return ersMISMATCHED "missing service name in 'service'";

            if( hash.find_value( cts[1] ) )
                return ersMISMATCHED "service name already specified in 'service'";

            service_params sp;
            sp._name = cts[1];
            sp._port = 0;
            sp._load = false;
            
            hash.insert_value(sp);
        }
        else if( cts[0] == "device" )
        {
            if( cts[1].is_empty() )
                return ersMISMATCHED "missing device name in 'device'";

            //just ignored now
        }
        else if( cts[0] == "!device" )
        {
            if( cts[1].is_empty() )
                return ersMISMATCHED "missing device name in '!device'";

            *_nodevlist.add() = cts[1];
        }
        else
        {
            return ersMISMATCHED "unknown root token in dev.conf";
        }
    }

    _hash.swap(hash);
    _fload_all = fdef >= 0;

    if( _wds.size() == 0 )
    {
        wdx += "device";
        if( directory::is_valid_directory( wdx ) )
        {
            wdx.append( directory::separator() );
            *_wds.add() = wdx;
        }
        else
            return ersUNAVAILABLE "no device dir found";
    }

    _cfg_loaded = true;

    return 0;
}

////////////////////////////////////////////////////////////////////////////////
void* PluginScannerCoid::thread_update_plugin_list( void* p )
{
    int count = INT_MAX;
    int flg;

    while( 0 != (flg = SRVROOTA.run_update_plglist()) )
    {
        if( ((PluginScannerCoid*)p)->is_exit_signalled() )  break;

        if (flg > 1)
            count = INT_MAX;

        if( count > 3600 )
        {
            ((PluginScannerCoid*)p)->update_plugin_list();
            count = 0;
        }
        ++count;
        sysSleep(1);
    }

    ((PluginScannerCoid*)p)->post_trace(1) << "Exiting the plugin updater thread" << EOM;
    return p;
}

////////////////////////////////////////////////////////////////////////////////
opcd PluginScannerCoid::init()
{
    _scit = 0;

    charstr buf;

    directory::get_cwd(buf);
    buf << "coid";

    opcd e;
    if( !_cfg_loaded )
    {
        e = load_cfg(true);
        if(e)
        {
            post_error(0) << "error loading configuration file 'dev.conf' : " << opcd_formatter(e) << EOM;
            return e;
        }
    }

    binstream& bin = post_info(0);
    bin << "device directories:";
    {for( uint i=0; i<_wds.size(); ++i )
        bin << "\n" << _wds[i];
    }
    bin << EOM;

    NODEREF<PluginCoid> plg;
    post_open() << "loading core services" << EOM;

    local<PluginCoid> plug = new PluginCoid(buf);

    //e = plug->load();
    //if(e)  return e;

    SRVROOTA.attach( plug.eject(), plg, 0, 0 );
    plg->init("core");

    dynarray< NODEREF<ServiceCoid> > newsvc;

    AcceptSvc AS(*this);
    plg->update_core_services( newsvc, &AS );

    dependency_sort(newsvc);

    for( uint i=0; i<newsvc.size(); ++i )
    {
        if( newsvc[i]->_desc._flags & ServiceDescriptor::fAUTORUN )
            newsvc[i]->spawn_auto_instance();

        if( newsvc[i]->_desc._acceptor_port != UMAX32  &&  newsvc[i]->_desc._acceptor_port != 0 )
        {
            //create special acceptor
            SRVROOTA.start_acceptor( newsvc[i], newsvc[i]->_desc._acceptor_port );
        }
    }

    plg.unset();

    return 0;
}


////////////////////////////////////////////////////////////////////////////////
opcd PluginScannerCoid::scan_directory( directory& dir, AcceptSvcCbk& AS, dynarray< NODEREF<ServiceCoid> >& newsvc )
{
    while( dir.next() )
    {
        if( dir.is_entry_directory() )
        {
            const char* fn = dir.get_last_file_name();
            if( fn && fn[0] != '.' )
            {
                directory dirdev;
                dirdev.open( dir.get_last_full_path(), "*.dev" );

                while( dirdev.next() )
                {
                    if( dirdev.is_entry_regular() )
                        load_device( dirdev, AS, newsvc );
                }
            }
        }
        else if( dir.is_entry_regular() && dir.get_last_file_name_token().ends_with_icase(".dev") )
            load_device( dir, AS, newsvc );
    }

    return 0;
}

////////////////////////////////////////////////////////////////////////////////
bool PluginScannerCoid::check_device_load( token fname )
{
    if( fname.ends_with_icase(".dev") )
        fname.shift_end(-4);

    for( uint i=0; i < _nodevlist.size(); ++i )
        if( _nodevlist[i].cmpeqi(fname) )
            return false;

    return true;
}

////////////////////////////////////////////////////////////////////////////////
opcd PluginScannerCoid::load_device( directory& dirdev, AcceptSvcCbk& AS, dynarray< NODEREF<ServiceCoid> >& newsvc )
{
    NODEREF<PluginCoid> plg;

    if( !check_device_load( dirdev.get_last_file_name() ) )
        return 0;
    
    charstr name;
    name << dirdev.get_last_file_name() << "|";

    token t = dirdev.get_last_file_name();
    t.cut_right_back('.');
    name << t << " service container";

    dynarray<uint> lstp;
    opcd e = SRVROOTA._objmgr.find_nodes_of_class( 0, PluginCoid::GET_INTERFACE(), name, lstp );
    
    if( e  ||  lstp.size() == 0 )
    {
        post_open() << "loading plugin: " << dirdev.get_last_file_name() << EOM;

        local<PluginCoid> plug = new PluginCoid( dirdev.get_last_full_path() );

        charstr cwd;
        directory::get_cwd(cwd);

        directory::chdir( dirdev.get_last_dir() );
        e = plug->load();

        directory::chdir(cwd);
        
        if(!e) {
            e = SRVROOTA.attach( plug.eject(), plg, 0, 0 );
            plg->init(name);
        }
    }
    else
    {
        e = SRVROOTA._objmgr.get_node( lstp[0], plg );
    }

    //mark the plugin as checked in this iteration
    if(!e)
    {
        plg->_scit = _scit;
        uints ns = newsvc.size();
        e = plg->update_service_list( newsvc, &AS );
        if(e) {
            post_warning(0) << "no services loaded from " << name << EOM;
            newsvc.need(ns);
        }
    }
    else
        post_error(0) << "error loading " << name << ", error: " << opcd_formatter(e) << EOM;

    if(!e)
        plg.unset();
    return e;
}

////////////////////////////////////////////////////////////////////////////////
///Scan for new plugins
opcd PluginScannerCoid::update_plugin_list()
{
    //CoidAcceptorCoid* accp = (CoidAcceptorCoid*) SRVROOTA.get_superior( get_id() );

    ++_scit;

    AcceptSvc   AS(*this);

    //post_info(0) << "scanning device directory for plugins" << EOM;
    dynarray< NODEREF<ServiceCoid> > newsvc;

    for( uint di=0; di<_wds.size(); ++di )
    {
        //browse directories under the device/ subdir
        directory dir;
        opcd e = dir.open( _wds[di], "*" );
        if(!e)
            e = scan_directory( dir, AS, newsvc );
        if(e)
        {
            post_warning(0) << "error opening/scanning the directory '" << _wds[di] << "'" << EOM;
            continue;
        }
    }

    /*
    NODEREF<PluginCoid> k;
    SRVROOTA.get_node( 0, k );
    if( k.goto_first_of_class(PluginCoid::GET_INTERFACE()) )
    {
        for(;;)
        {
            if( ((PluginCoid*)k->_node)->_scit != _scit  &&  ((PluginCoid*)k->_node)->_name != "core" )
            {
                //post_close() << "removing plugin:" << k->_name << EOM;
                post_info(0) << "missing plugin:" << k->_node->_name << EOM;
            }

            if( !k.goto_next_of_class(PluginCoid::GET_INTERFACE()) )  break;
        }
    }
    */
    opcd e = dependency_sort(newsvc);
    if(e) {
        binstream& bin = post_warning(0);
        bin << "dependency ordering failed, automatic instances of: ";
        for( uint i=0; i<newsvc.size(); ++i )
            bin << "\n  " << newsvc[i]->_desc._name;
        bin << "\nwill not be created!" << EOM;
        return e;
    }

    PluginCoid::run_services(newsvc);
    return 0;
}

////////////////////////////////////////////////////////////////////////////////
//sort by dependency
opcd PluginScannerCoid::dependency_sort( dynarray< NODEREF<ServiceCoid> >& svc )
{
    opcd e=0;
    for( uint i=0; i<svc.size(); )
    {
        if( svc[i]->_sorted ) { ++i; continue; }

        uint n=0;
        for( uint d=0; svc[i]->_desc._dependencies[d]; ++d )
        {
            uint ix = find_svc_in_list( svc, svc[i]->_desc._dependencies[d] );

            if( ix != UMAX32  &&  ix > i )
            {
                svc.move( ix, i, 1 );
                ++i;
                ++n;
            }
        }
        svc[i]->_sorted = true;
        if(n)
            i -= n;
        else
            ++i;
    }

    if( svc.size() )
    {
        binstream& m = (post_debug(1) << "loading order:\n");
        for( uint k=0; k<svc.size(); ++k )
        {
            m << ' ' << svc[k]->_desc._name;
        }
        m << EOM;
    }

    {
        uint err=0;
        for( uint i=0; i<svc.size(); ++i )
        {
            for( uint j=0; svc[i]->_desc._dependencies[j]; ++j )
            {
                uint ix = find_svc_in_list( svc, svc[i]->_desc._dependencies[j] );
                if( ix != UMAX32  &&  ix > i )
                {
                    post_error(0) << "can't obey: " << svc[i]->_desc._name << " < " << svc[i]->_desc._dependencies[j] << EOM;
                    ++err;
                }
            }
        }

        if(err)
        {
            post_error(0) << "bad loading order: " << err << " dependency links disobeyed" << EOM;
            e = ersINVALID_PARAMS "bad loading order";
        }
/*        else
        {
            binstream& m = (post_trace(0) << "dependencies:\n");
            for (uint i=0; i<svc.size(); ++i)
            {
                m << "    " << svc[i]->_desc._name;
                for (uint j=0; svc[i]->_desc._dependencies[j]; ++j)
                    m << " < " << svc[i]->_desc._dependencies[j];
                m << '\n';
            }
            m << EOM;
        }*/
    }

    return e;
}

}
